/*
 * Paging-based user mode implementation
 * Copyright (c) 2003,2004 David H. Hovemeyer <daveho@cs.umd.edu>
 * $Revision: 1.50 $
 * 
 * This is free software.  You are permitted to use,
 * redistribute, and modify it as specified in the file "COPYING".
 */

#include <geekos/int.h>
#include <geekos/mem.h>
#include <geekos/paging.h>
#include <geekos/malloc.h>
#include <geekos/string.h>
#include <geekos/argblock.h>
#include <geekos/kthread.h>
#include <geekos/range.h>
#include <geekos/vfs.h>
#include <geekos/user.h>
#include <geekos/errno.h>
/*CREO QUE DEBEMOS AGREGAR ESTO*/
#include <geekos/gdt.h>



/*#define DEFAULT_USER_STACK_SIZE 8192*/
#define DEFAULT_USER_STACK_SIZE 4096
extern void Load_LDTR(ushort_t);


/* ----------------------------------------------------------------------
 * Private functions
 * ---------------------------------------------------------------------- */

// TODO: Add private functions.
/*static bool Validate_User_Memory(struct User_Context* userContext,
    ulong_t userAddr, ulong_t bufSize)
{
    ulong_t avail;

    if (userAddr >= userContext->size)
        return false;

    avail = userContext->size - userAddr;
    if (bufSize > avail)
        return false;

    return true;
}*/
/* ----------------------------------------------------------------------
 * Public functions
 * ---------------------------------------------------------------------- */



/*
 * Destroy a User_Context object, including all memory
 * and other resources allocated within it.
 */
void Destroy_User_Context(struct User_Context* context)
{
    /*
     * Hints:
     * - Free all pages, page tables, and page directory for
     *   the process (interrupts must be disabled while you do this,
     *   otherwise those pages could be stolen by other processes)
     * - Free semaphores, files, and other resources used
     *   by the process
     */
    TODO("Destroy User_Context data structure after process exits");
 
}

/*
 * Load a user executable into memory by creating a User_Context
 * data structure.
 * Params:
 * exeFileData - a buffer containing the executable to load
 * exeFileLength - number of bytes in exeFileData
 * exeFormat - parsed ELF segment information describing how to
 *   load the executable's text and data segments, and the
 *   code entry point address
 * command - string containing the complete command to be executed:
 *   this should be used to create the argument block for the
 *   process
 * pUserContext - reference to the pointer where the User_Context
 *   should be stored
 *
 * Returns:
 *   0 if successful, or an error code (< 0) if unsuccessful
 */
 
int Load_User_Program(char *exeFileData, ulong_t exeFileLength, struct Exe_Format *exeFormat, const char *command, struct User_Context **pUserContext)
{   
	unsigned numArgs;   /*Numero de argumentos que tiene el main del ejecutable. La carga Get_Argument_Block_Size*/
    int dirIndex = 0, tableIndex= 0, pageIndex = 0;	/*Indices del directorio, de la tabla y de la pagina*/ 
    int segmento = 0, j = 0;	/*Indices de ciclos*/ 
    int datosACopiar = 0, tamLibrePage = 0, cuantoCopiar = 0, nPages = 0;
    int topva = 0, maxva = 0;
    ulong_t argBlockSize, vaddrss = 0;   /*Tamaño del bloque de argumentos del main del ejecutable. La carga Get_Argument_Block_Size. maxva=Almacena el maximo tamaño de los segmentos en memoria*/
    pde_t * userDir = 0;	/*directorio de paginas del usuario*/
    pte_t * pageTable = 0;	/*tabla de paginas del usuario*/
    void *page = 0;	/*pagina de memoria*/
    
    /*Inicializacion del Directorio de paginas*/ 
    userDir = (pde_t *) Alloc_Page();
    if (userDir == 0)
		goto falloFaltaMemoria;
	memset((void *)userDir, '\0',PAGE_SIZE);
	memcpy(userDir, (pde_t *) Get_PDBR(), PAGE_SIZE/2);	/*copia el directorio de paginas del kernel a directoriode paginas del usuario*/
	
	/*Inicilizacion de la estructura *pUserContext */
	*pUserContext = (struct User_Context *) Alloc_Page();
	if ((*pUserContext) == 0){
        goto falloFaltaMemoria;
    }
	memset(*pUserContext, '\0',PAGE_SIZE);
	
	/* Busca la maxima direccion virtual, lo hace para calcular el tamaño que ocupan los segmentos del ejecutable */
	for (segmento = 0; segmento < exeFormat->numSegments; ++segmento) {
		struct Exe_Segment *segment = &exeFormat->segmentList[segmento];
		topva = segment->startAddress + segment->sizeInMemory; 
		
		if (topva > maxva)
			maxva = topva;
    }
    (*pUserContext)->size = maxva; /*Calcula el tamaño total de la memoria del ejecutable*/
    
	/* Reserva memoria para el programa */
	nPages = Round_Up_To_Page(maxva) / PAGE_SIZE;
	//Print("maxva: %d, nPages: %d\n", maxva, nPages);
	for(j = 0; j < nPages; j++){
		vaddrss = j*PAGE_SIZE + USER_VM_START;
		if (Create_Page_Table( userDir, &pageTable, vaddrss, (VM_READ | VM_WRITE | VM_USER)) < 0)
			goto falloFaltaMemoria_memory;
		if (Create_User_Page(pageTable, vaddrss, &page) < 0)
			goto falloFaltaMemoria_memory;
		Print("pageTableBaseAddr: %lu\n", (ulong_t) pageTable);
		Print("page: %lu, va: %lu\n", (ulong_t) page, vaddrss);
	}

   
    //(*pUserContext)->memory = (char *) USER_VM_START;//esto no se usa, no tiene sentido
	
	Print("*****************************************************\n");
	
	/*Cargamos los segmentos del programa en la memoria de usuario*/
	for (segmento = 0; segmento < exeFormat->numSegments; segmento++){
		nPages = Round_Up_To_Page(exeFormat->segmentList[segmento].sizeInMemory)/PAGE_SIZE;
		if (nPages > 0){
			/*Round_Down_To_Page para que nos de la direccion de comienzo de la pagina que contiene a esa direccion virtual.*/
			vaddrss = Round_Down_To_Page((exeFormat->segmentList[segmento].startAddress) + USER_VM_START);	
			datosACopiar = exeFormat->segmentList[segmento].sizeInMemory;
			Print("segmento %d, nPages %d, direccion del segmento: %lu, vaddrss %lu, datosACopiar: %d\n",segmento,nPages,exeFormat->segmentList[segmento].startAddress,vaddrss,datosACopiar);
			
			for (j = 0; j < nPages; j++){
				
				pageIndex = PAGE_INDEX((exeFormat->segmentList[segmento].startAddress) + USER_VM_START);
				
				if (Create_Page_Table( userDir, &pageTable, vaddrss, (VM_READ | VM_WRITE | VM_USER)) < 0)
					goto falloFaltaMemoria_memory;
				if (Create_User_Page(pageTable, vaddrss, &page) < 0)
					goto falloFaltaMemoria_memory;
				

				Print("------------pageIndex: %d vaddrss %lu\n",pageIndex,vaddrss);
					
				tamLibrePage = PAGE_SIZE - pageIndex;
				Print("tamLibrePage %d\n",tamLibrePage);
				if(datosACopiar <= tamLibrePage)
					cuantoCopiar = datosACopiar;
				else {
					cuantoCopiar = tamLibrePage;
					datosACopiar = datosACopiar-tamLibrePage;
				}
				Print("cuantoCopiar %d, direccionVirtual %lu, direccion inicio de copiado en segmento %lu, dirFisicaACopiar %lu\n",cuantoCopiar,vaddrss, (exeFileData + exeFormat->segmentList[segmento].offsetInFile) + (j * PAGE_SIZE), ((ulong_t)page) + (pageIndex));
				memcpy(((void*)(((ulong_t)page) + (pageIndex))), (exeFileData + exeFormat->segmentList[segmento].offsetInFile) + (j * PAGE_SIZE), cuantoCopiar);	
				vaddrss = Round_Down_To_Page(vaddrss + PAGE_SIZE);
			}
			
		}
	}
//TODO("Hasta aca vamos bien dijo un pollo y estaba en la puerta del horno!!!");
	/*copiar parametros en vaddrss, la vaddrss esta bien, sale con el valor bien puesto desde el for!!!*/
	Get_Argument_Block_Size(command, &numArgs, &argBlockSize);  /*Obtiene el tamaño de bloque para los argumentos y la cantidad de argumentos.*/
	(*pUserContext)->argBlockAddr = vaddrss;  /*Porque el bloque de argumentos va a continuacion de los segmentos*/
	KASSERT(argBlockSize <= PAGE_SIZE);
	
	if (Create_Page_Table( userDir, &pageTable, vaddrss, (VM_READ | VM_WRITE | VM_USER)) < 0)
		goto falloFaltaMemoria_memory;
	if (Create_User_Page(pageTable, vaddrss, &page) < 0)
		goto falloFaltaMemoria_memory;
	Format_Argument_Block((char*) page, numArgs, vaddrss, command);
	
    (*pUserContext)->entryAddr = (exeFormat->entryAddr) + USER_VM_START;  /*Cargamos el punto desde donde debe empezar a ejecutarse el programa*/
    /*controlar que  entry addres sea un adeirccion dentro del segmento de codigo*/
    /*Reservamos lugar para el stack en la ultima direccion virtual*/
    vaddrss = Round_Down_To_Page(USER_VM_END);
	if (Create_Page_Table(userDir, &pageTable, vaddrss, (VM_READ | VM_WRITE | VM_USER)) < 0)
		goto falloFaltaMemoria_memory;
	if (Create_User_Page(pageTable, vaddrss, &page) < 0)
		goto falloFaltaMemoria_memory;

    (*pUserContext)->stackPointerAddr = USER_VM_END;   /*offset, es la ultima direccion de memory*/
 
	/* Set user page directory */
    (*pUserContext)->pageDir = userDir;
    
    /* Aloca e inicializa los descriptores y selectores de codigo y dato */
    
    
    //(*pUserContext)->ldtDescriptor = Allocate_Segment_Descriptor();  /*devuelve un puntero a la GDT*/
//    if ((*pUserContext)->ldtDescriptor == 0)
//        goto falloFaltaMemoria_memory;
//        
//    (*pUserContext)->ldtSelector = Selector( KERNEL_PRIVILEGE, true, Get_Descriptor_Index( (*pUserContext)->ldtDescriptor )); 
//    Init_LDT_Descriptor((*pUserContext)->ldtDescriptor,(*pUserContext)->ldt,NUM_USER_LDT_ENTRIES);
//    Init_Code_Segment_Descriptor((*pUserContext)->ldt,(ulong_t)(*pUserContext)->memory,((*pUserContext)->size/PAGE_SIZE),USER_PRIVILEGE);
//    Init_Data_Segment_Descriptor((*pUserContext)->ldt + 1,(ulong_t)(*pUserContext)->memory,((*pUserContext)->size/PAGE_SIZE),USER_PRIVILEGE);
//    (*pUserContext)->csSelector = Selector( USER_PRIVILEGE, false, 0);   
//	(*pUserContext)->dsSelector = Selector(USER_PRIVILEGE, false, 1);   
    (*pUserContext)->refCount = 0; 
  
    return 0;

falloFaltaMemoria_memory:
    if ((*pUserContext) != 0){
		//Destroy_User_Context(*pUserContext);
        Free(*pUserContext);
		Print("Fallo Load_User_Program ");
	}
falloFaltaMemoria:
    return ENOMEM;  /*Error por fallo en Malloc*/
	
}

/*
 * Copy data from user buffer into kernel buffer.
 * Returns true if successful, false otherwise.
 */
bool Copy_From_User(void* destInKernel, ulong_t srcInUser, ulong_t numBytes)
{
    /*
     * Hints:
     * - Make sure that user page is part of a valid region
     *   of memory
     * - Remember that you need to add 0x80000000 to user addresses
     *   to convert them to kernel addresses, because of how the
     *   user code and data segments are defined
     * - User pages may need to be paged in from disk before being accessed.
     * - Before you touch (read or write) any data in a user
     *   page, **disable the PAGE_PAGEABLE bit**.
     *
     * Be very careful with race conditions in reading a page from disk.
     * Kernel code must always assume that if the struct Page for
     * a page of memory has the PAGE_PAGEABLE bit set,
     * IT CAN BE STOLEN AT ANY TIME.  The only exception is if
     * interrupts are disabled; because no other process can run,
     * the page is guaranteed not to be stolen.
     */
     /*controlo que la cantidad de memoria no se vaya de rango*/
    TODO("Copy user data to kernel buffer");

}

/*
 * Copy data from kernel buffer into user buffer.
 * Returns true if successful, false otherwise.
 */
bool Copy_To_User(ulong_t destInUser, void* srcInKernel, ulong_t numBytes)
{
    /*
     * Hints:
     * - Same as for Copy_From_User()
     * - Also, make sure the memory is mapped into the user
     *   address space with write permission enabled
     */
    TODO("Copy kernel data to user buffer");

}

/*
 * Switch to user address space.
 */
void Switch_To_Address_Space(struct User_Context *userContext)
{
    /*
     * - If you are still using an LDT to define your user code and data
     *   segments, switch to the process's LDT
     * - TODO("Switch_To_Address_Space() using paging"); 
     */
    TODO("espero que no sea esto!!!");   
    /* Load the LDT with user ldt */   
    Load_LDTR(userContext->ldtSelector);
    /* Set the user pageDir as the current page directory */
    Set_PDBR(userContext->pageDir);   
}


